package jlib.component.scroll
{
	import flash.display.DisplayObject;
	import flash.display.InteractiveObject;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	
	import jlib.utils.ArrangeUtil;
	import jlib.utils.DepthUtil;
	
	/**
	 * @example <listing version="3.0" >
	 * var scrollBar:ScrollBar = new ScrollBar();
	 * scrollBar.setMoveDistance(0.1); 							   // increase 메서드나 decrease 메서드 호출 되면 이동하는 거리 설정. (상/하 버튼과 같이 사용시 사용하세요.)
	 * scrollBar.setTrack(ScrollBarDefaultElement.VERTICAL_TRACK); // 스크롤바 track 디스플레이 객체 설정.
	 * scrollBar.setThumb(ScrollBarDefaultElement.VERTICAL_THUMB); // 스크롤바 thumb 디스플레이 객체 설정.
	 * scrollBar.setDirection(ScrollBarDirection.VERTICAL);		   // 스크롤바의 방향설정.
	 * scrollBar.setTarget(panel);								   // 스크롤 되는 타겟 설정.
	 * scrollBar.setGuide(50); 									   // 스크롤 방향이 vertical일 경우 target의 y축 시작 위치 설정.
	 * scrollBar.setTrackSize(100);								   // 스크롤바 TrackSize 
	 * scrollBar.setRange(50);	 								   // 트랙싸이즈 100 px 일때 움직일 수 있는 범위가 50 정도 됨.
	 * </listing>
	 * @author park jong hee
	 */	
	public class ScrollBar extends Sprite
	{
		protected var m_target:DisplayObject;
		protected var m_track:DisplayObject;
		protected var m_thumb:InteractiveObject;
		
		protected var m_startPoint:Point;
		
		protected var m_direction:String;
		protected var m_directionState:IScrollBarDirectionState;
		protected var m_trackManager:ScrollTrackManager;
		
		protected var m_range:Number;
		protected var m_guide:Number;
		
		protected var m_moveDistance:Number;
		protected var m_interactable:Boolean;
		protected var m_thumbScalable:Boolean;
		protected var m_isEnabled:Boolean;
		
		protected var m_downPosition:Point;
		protected var m_thumbPosition:Point;
		
		protected var m_wheelSpeed:Number;
		protected var m_wheelArea:Rectangle;
		
		protected var m_bChangedWheelArea:Boolean;
		
		protected var m_isAutoRange:Boolean;
		
		protected var m_thumbStartOffset:Number;
		protected var m_thumbEndOffset:Number;
		/**
		 * 스크롤바 생성자.
		 */		
		public function ScrollBar()
		{
			super();
			buildDefault();
		}
		
		private function buildDefault(): void
		{
			m_thumbStartOffset = 0;
			m_thumbEndOffset = 0;
			m_moveDistance = 0.1;
			m_isAutoRange = true;
			m_thumbScalable = true;
			setInteractivable(true);
			m_startPoint = new Point(0,0);
			m_downPosition = new Point();
			m_thumbPosition = new Point();
			m_guide = m_range = 0;
			m_direction = ScrollBarDirection.VERTICAL;
			setDirection(m_direction);
			setWheelSpeed(1);
			setEnabled(true);
		}
		
		/**
		 * 스크롤 되는 객체를 반환합니다. 
		 * @return scroll target 객체.
		 */		 
		public function getTarget():DisplayObject { return m_target; }
		
		/**
		 * 스크롤 되는 객체를 설정합니다. 
		 * @param $target scroll target 객체.
		 */		
		public function setTarget($target:DisplayObject):void 
		{
			if(m_target) m_target.removeEventListener(MouseEvent.MOUSE_WHEEL, onWheelControl );
			m_target = $target;
			
			if(stage == null) this.addEventListener(Event.ADDED_TO_STAGE, initWheel );
			else initWheel();
			update();
		}
		
		private function initWheel(event:Event=null):void
		{
			stage.addEventListener(MouseEvent.MOUSE_WHEEL, onWheelControl );
			m_wheelArea = m_target.getBounds(stage);
		}
		/**
		 * Wheel이 가능한 영역을 반환합니다.
		 * @return Rectangle 객체.
		 */
		public function getWheelArea(): Rectangle { return m_wheelArea; }
		/**
		 * Wheel이 가능한 영역을 설정합니다.  
		 * @param $area Rectangle 객체.
		 */		
		public function setWheelArea($area:Rectangle): void {
			m_wheelArea = $area;
			m_bChangedWheelArea = true;
		}
		
		/**
		 * 스크롤바의 Track 디스플레이 객체를 반환합니다. 
		 * @return Scrollbar Track
		 */		
		public function getTrack():DisplayObject { return m_track; }
		/**
		 * 스크롤바의 Track 디스플레이를 설정합니다. 
		 * @param $track 디스플레이 객체
		 */		
		public function setTrack($track:DisplayObject):void 
		{ 
			if(m_track) removeChild(m_track);
			m_track = $track;
			m_trackManager.update();
			update();
		}
		
		/**
		 * 스크롤바의 Thumb 디스플레이 객체를 반환합니다. 
		 * @return Scrollbar Thumb
		 */		
		public function getThumb(): InteractiveObject{ return m_thumb; }
		/**
		 * 스크롤바의 Thumb 디스플레이를 설정합니다. 
		 * @param $thumb 디스플레이 객체
		 */		
		public function setThumb($thumb:InteractiveObject): void 
		{
			if(m_thumb) removeChild(m_thumb);
			m_thumb = $thumb;
			m_trackManager.update();
			update();
		}
		
		/**
		 * 스크롤 범위 값을 반환합니다.
		 * @return 범위 값.
		 */		
		public function getRange(): Number { return m_range; }
		/**
		 * 스크롤 되는 방향에 맞는 영역 값을 설정합니다. 설정하기 전엔 setTrackSize 와 같습니다.
		 * Scroll Target에 비례하는 값입니다. 보통 setTrackSize에 기본적으로 설정되지만 값을 넣으면
		 * 지정된 값으로 스크롤의 영역에 비례된 값이 움직입니다.  
		 * @example <listing version="3.0" >
		 * scrollbar.setTarget(bitmap) // bitmap.height = 100 
		 * scrollbar.setRange(50); // 범위를 50으로 지정하면, y 값이 50 만큼 이동합니다.
		 * </listing>
		 * @param $value 양의 값.
		 */		
		public function setRange($value:Number): void
		{
			if($value < 0) throw new Error("양수 값만 받을 수 있습니다.");
			m_range = $value;
			m_isAutoRange = false;
			update();
		}
		/**
		 * 스크롤바의 방향을 반환합니다. 
		 * @return 방향
		 */		
		public function getDirection():String { return m_direction; }
		/**
		 * 스크롤바의 방향을 설정합니다. 
		 * @param $direction 방향.
		 */		
		public function setDirection($direction:String): void 
		{ 
			m_direction = $direction;
			
			if(m_directionState) {
				m_directionState = null;
				m_trackManager.removeEventListener(Event.CHANGE, onChangeTargetPosition );
				m_trackManager.dispose();
				m_trackManager = null;
			}
			if(m_direction == ScrollBarDirection.VERTICAL){
				m_directionState = new ScrollBarVerticalState(this);
			} else{
				m_directionState = new ScrollBarHorizontalState(this);
			}
			m_trackManager = m_directionState as ScrollTrackManager;
			m_trackManager.addEventListener(Event.CHANGE, onChangeTargetPosition );
			update();
		}
		
		/**
		 * Thumb 디스플레이 객체의 스케일 가능 여부를 리턴합니다. 
		 * @return 스케일 boolean
		 */		
		public function getThumbScalable(): Boolean { return m_thumbScalable; }
		
		/**
		 * Thumb 디스플레이 객체의 스케일 가능 여부를 설정합니다.
		 * @param bool Thumb의 스케일 유무
		 */		
		public function setThumbScalable(bool:Boolean=true): void { m_thumbScalable = bool; }
		
		/**
		 * 입력 객체에 대한 스크롤바의 인터렉티브 (컨트롤) 유무를 반환합니다.
		 * @return boolean 객체.
		 */		
		public function getInteractivable(): Boolean { return m_interactable; }
		/**
		 * 입력 객체에 대한 스크롤바의 인터렉티브 (컨트롤) 유무를 설정합니다.
		 * @param $boolean
		 */		
		public function setInteractivable($boolean:Boolean): void 
		{ 
			m_interactable = $boolean;
			update();
		}
		/**
		 * Track 디스플레이 객체의 size 값을 반환합니다.
		 * @return size값
		 */		
		public function getTrackSize():Number { return m_directionState.getTrackSize(); }
		/**
		 * Track 디스플레이 객체의 size 값을 설정합니다.
		 * @param sizePixel size값
		 */		
		public function setTrackSize(sizePixel:Number): void  {
			if(m_isAutoRange) m_range = sizePixel;
			m_directionState.setTrackSize(sizePixel);
			update();
		}
		
		/**
		 * 스크롤바의 Enable 값을 반환합니다. 
		 * @return 스크롤바 enable boolean.
		 */		
		public function getEnabled():Boolean { return m_isEnabled; }
		/**
		 * 스크롤바의 Enable 값을 설정합니다.
		 * @param $boolean 스크롤바 enable
		 */		
		public function setEnabled($boolean:Boolean): void
		{
			m_isEnabled = $boolean
			if(m_thumb) {
				m_thumb.visible = $boolean;
				m_trackManager.setInteractiveable($boolean);
			}
		}
		
		/**
		 * 스크롤 target 객체의 시작 기준선을 리턴합니다.
		 * @return 시작 기준선 위치 값.
		 */		
		public function getGuide(): Number { return m_guide; }
		/**
		 * 스크롤 target 객체의 시작 기준선을 설정합니다.
		 * @example <listing version="3.0" > setGuide(100); // 세로스크롤바일 경우 y 값 100 부터 스크롤 됩니다. </listing>
		 * @param $guide 스크롤 시작 기준선.
		 */		
		public function setGuide($guide:Number): void
		{
			m_guide = $guide;
			update();
			onChangeTargetPosition();
		}
		/**
		 * increase / decrease 호출시 이동거리 값을 반환합니다.
		 * @return 이동거리 값.
		 */		
		public function getMoveDistance(): Number { return m_moveDistance; }
		/**
		 * increase / decrease 호출시 이동거리 [0.0-1.0] 값을 설정합니다.
		 * @param value 이동거리 값.
		 */		
		public function setMoveDistance(value:Number): void { m_moveDistance = value; }
		
		/**
		 * 포지션 값 [0.0-1.0] 을 반환합니다. 
		 * @return 
		 */		
		public function getPosition(): Number { return m_trackManager.getPosition(); }
		/**
		 * 포지션 값 [0.0~1.0] 을 설정합니다.
		 * @param rate
		 */		
		public function setPosition(rate:Number): void  { m_trackManager.setPosition(rate); }
		
		/**
		 * 휠 스피드 값을 반환합니다. 
		 * @return 휠 스피드 값.
		 */		
		public function getWheelSpeed(): Number { return m_wheelSpeed; }
		/**
		 * 휠 스피드 값을 설정합니다. 
		 * @param value 휠 스피드 값.
		 */				
		public function setWheelSpeed(value:Number): void { m_wheelSpeed = value; }
		
		/**
		 * 스크롤 바를 증가값으로 이동시킵니다. 
		 * @param e 이벤트 객체.
		 */		
		public function increase(e:Event=null): void 
		{ 
			m_trackManager.setPosition(m_trackManager.getPosition()+getMoveDistance());
		}
		/**
		 * 스크롤 바를 감소값으로 이동시킵니다. 
		 * @param e 이벤트 객체.
		 */		
		public function decrease(e:Event=null): void 
		{ 
			m_trackManager.setPosition(m_trackManager.getPosition()-getMoveDistance());
		}
		
		public function getThumbStartOffset(): Number { return m_thumbStartOffset; }
		
		public function setThumbStartOffset($value:Number): void {
			m_thumbStartOffset = $value;
		}
		
		public function getThumbEndOffset(): Number { return m_thumbEndOffset; }
		
		public function setThumbEndOffset($value:Number): void {
			m_thumbEndOffset = $value;
		}
		
		private function onWheelControl(e:MouseEvent): void {
			if(!m_isEnabled) return;
			if(!m_wheelArea.contains(e.stageX, e.stageY)) return ;
			if(!m_interactable) return ;
			
			m_trackManager.setPosition(m_trackManager.getPosition()-e.delta/(30/m_wheelSpeed));
		}
		
		private function onChangeTargetPosition(e:Event=null): void  {
			m_directionState.changeTargetPosition();
			if(e) dispatchEvent(new Event(Event.CHANGE));
		}
		
		public function update(e:Event=null): void 
		{
			if(!m_target) return;
			if(!m_bChangedWheelArea){
				if(m_target.scrollRect) {
					m_wheelArea = m_target.scrollRect;
				}else {
					if(stage) m_wheelArea = m_target.getBounds(stage);
				}
			}
			if(m_track && m_thumb) {
				DepthUtil.align([m_track, m_thumb],this);
			}
			m_trackManager.update();
			if(m_thumbScalable) m_directionState.changeThumbScale();
			if(m_isEnabled) {
				m_thumb.visible = m_directionState.hasScrollBar(); 
				m_trackManager.setInteractiveable(m_thumb.visible);
			}
			m_trackManager.setInteractiveable(m_interactable);
		}
		
		internal function getTrackManager(): ScrollTrackManager { return m_trackManager; }
		
		public function isMaxPosition(): Boolean { return ( getPosition() == 1 ); }
		
		public function isMinPosition(): Boolean { return ( getPosition() == 0 ); }
		
		
	}//c
}